/*!
 * imagesLoaded v3.0.2
 * JavaScript is all like "You images are done yet or what?"
 */

(function (window) {

    'use strict';

    var $ = window.jQuery;
    var console = window.console;
    var hasConsole = typeof console !== 'undefined';

// -------------------------- helpers -------------------------- //

// extend objects
    function extend(a, b) {
        for (var prop in b) {
            a[prop] = b[prop];
        }
        return a;
    }

    var objToString = Object.prototype.toString;

    function isArray(obj) {
        return objToString.call(obj) === '[object Array]';
    }

// turn element or nodeList into an array
    function makeArray(obj) {
        var ary = [];
        if (isArray(obj)) {
            // use object if already an array
            ary = obj;
        } else if (typeof obj.length === 'number') {
            // convert nodeList to array
            for (var i = 0, len = obj.length; i < len; i++) {
                ary.push(obj[i]);
            }
        } else {
            // array of single index
            ary.push(obj);
        }
        return ary;
    }

// --------------------------  -------------------------- //

    function defineImagesLoaded(EventEmitter, eventie) {

        /**
         * @param {Array, Element, NodeList, String} elem
         * @param {Object or Function} options - if function, use as callback
         * @param {Function} onAlways - callback function
         */
        function ImagesLoaded(elem, options, onAlways) {
            // coerce ImagesLoaded() without new, to be new ImagesLoaded()
            if (!(this instanceof ImagesLoaded)) {
                return new ImagesLoaded(elem, options);
            }
            // use elem as selector string
            if (typeof elem === 'string') {
                elem = document.querySelectorAll(elem);
            }

            this.elements = makeArray(elem);
            this.options = extend({}, this.options);

            if (typeof options === 'function') {
                onAlways = options;
            } else {
                extend(this.options, options);
            }

            if (onAlways) {
                this.on('always', onAlways);
            }

            this.getImages();

            if ($) {
                // add jQuery Deferred object
                this.jqDeferred = new $.Deferred();
            }

            // HACK check async to allow time to bind listeners
            var _this = this;
            setTimeout(function () {
                _this.check();
            });
        }

        ImagesLoaded.prototype = new EventEmitter();

        ImagesLoaded.prototype.options = {};

        ImagesLoaded.prototype.getImages = function () {
            this.images = [];

            // filter & find items if we have an item selector
            for (var i = 0, len = this.elements.length; i < len; i++) {
                var elem = this.elements[i];
                // filter siblings
                if (elem.nodeName === 'IMG') {
                    this.addImage(elem);
                }
                // find children
                var childElems = elem.querySelectorAll('img');
                // concat childElems to filterFound array
                for (var j = 0, jLen = childElems.length; j < jLen; j++) {
                    var img = childElems[j];
                    this.addImage(img);
                }
            }
        };

        /**
         * @param {Image} img
         */
        ImagesLoaded.prototype.addImage = function (img) {
            var loadingImage = new LoadingImage(img);
            this.images.push(loadingImage);
        };

        ImagesLoaded.prototype.check = function () {
            var _this = this;
            var checkedCount = 0;
            var length = this.images.length;
            this.hasAnyBroken = false;
            // complete if no images
            if (!length) {
                this.complete();
                return;
            }

            function onConfirm(image, message) {
                if (_this.options.debug && hasConsole) {
                    console.log('confirm', image, message);
                }

                _this.progress(image);
                checkedCount++;
                if (checkedCount === length) {
                    _this.complete();
                }
                return true; // bind once
            }

            for (var i = 0; i < length; i++) {
                var loadingImage = this.images[i];
                loadingImage.on('confirm', onConfirm);
                loadingImage.check();
            }
        };

        ImagesLoaded.prototype.progress = function (image) {
            this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
            this.emit('progress', this, image);
            if (this.jqDeferred) {
                this.jqDeferred.notify(this, image);
            }
        };

        ImagesLoaded.prototype.complete = function () {
            var eventName = this.hasAnyBroken ? 'fail' : 'done';
            this.isComplete = true;
            this.emit(eventName, this);
            this.emit('always', this);
            if (this.jqDeferred) {
                var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
                this.jqDeferred[jqMethod](this);
            }
        };

        // -------------------------- jquery -------------------------- //

        if ($) {
            $.fn.imagesLoaded = function (options, callback) {
                var instance = new ImagesLoaded(this, options, callback);
                return instance.jqDeferred.promise($(this));
            };
        }


        // --------------------------  -------------------------- //

        var cache = {};

        function LoadingImage(img) {
            this.img = img;
        }

        LoadingImage.prototype = new EventEmitter();

        LoadingImage.prototype.check = function () {
            // first check cached any previous images that have same src
            var cached = cache[this.img.src];
            if (cached) {
                this.useCached(cached);
                return;
            }
            // add this to cache
            cache[this.img.src] = this;

            // If complete is true and browser supports natural sizes,
            // try to check for image status manually.
            if (this.img.complete && this.img.naturalWidth !== undefined) {
                // report based on naturalWidth
                this.confirm(this.img.naturalWidth !== 0, 'naturalWidth');
                return;
            }

            // If none of the checks above matched, simulate loading on detached element.
            var proxyImage = this.proxyImage = new Image();
            eventie.bind(proxyImage, 'load', this);
            eventie.bind(proxyImage, 'error', this);
            proxyImage.src = this.img.src;
        };

        LoadingImage.prototype.useCached = function (cached) {
            if (cached.isConfirmed) {
                this.confirm(cached.isLoaded, 'cached was confirmed');
            } else {
                var _this = this;
                cached.on('confirm', function (image) {
                    _this.confirm(image.isLoaded, 'cache emitted confirmed');
                    return true; // bind once
                });
            }
        };

        LoadingImage.prototype.confirm = function (isLoaded, message) {
            this.isConfirmed = true;
            this.isLoaded = isLoaded;
            this.emit('confirm', this, message);
        };

        // trigger specified handler for event type
        LoadingImage.prototype.handleEvent = function (event) {
            var method = 'on' + event.type;
            if (this[method]) {
                this[method](event);
            }
        };

        LoadingImage.prototype.onload = function () {
            this.confirm(true, 'onload');
            this.unbindProxyEvents();
        };

        LoadingImage.prototype.onerror = function () {
            this.confirm(false, 'onerror');
            this.unbindProxyEvents();
        };

        LoadingImage.prototype.unbindProxyEvents = function () {
            eventie.unbind(this.proxyImage, 'load', this);
            eventie.unbind(this.proxyImage, 'error', this);
        };

        // -----  ----- //

        return ImagesLoaded;
    }

// -------------------------- transport -------------------------- //

    if (typeof define === 'function' && define.amd) {
        // AMD
        define([
                'eventEmitter',
                'eventie'
            ],
            defineImagesLoaded);
    } else {
        // browser global
        window.imagesLoaded = defineImagesLoaded(
            window.EventEmitter,
            window.eventie
        );
    }

})(window);